package com.aepryus.loom;

import java.util.*;
import java.lang.reflect.*;

public abstract class Domain {
	private int status = LOADING;
	private boolean bound = false;

	private Task task;

	int getStatus () {return status;}
	public boolean isLoading () {return status == LOADING;}
	public boolean isNew () {return status == CREATE;}
	public boolean isBound () {return bound;}

	public abstract long getID();
	public long getId () {return getID();}
	public abstract String getDomain();
	public abstract Domain getParentDomain () throws PersistenceException;
	public abstract String getDisplay() throws PersistenceException;

	public void validate () throws ValidationException,PersistenceException {}

	protected void onCreate () throws PersistenceException {}
	protected void onEdit () throws LockException,PersistenceException,ValidationException {}
	protected void onDelete () throws LockException,PersistenceException,ValidationException {}
	protected void onPersist () throws LockException,PersistenceException,ValidationException {}
	protected void onUnbind () {}

	void bind () {
		bound = true;
	}
	void unbind () {
		bound = false;
		onUnbind();
	}

	protected synchronized void access () {
		if (status != LOADING && !bound) throw new RuntimeException("Construction or modifications of an unbound domain object have been attempted.  Domain objects can not be constructed directly; use DomainPort to create or select Domain Objects.  Also, changes can not be made to domain objects that are not in the cache.  Domain Objects should not be held on to after they have been removed from the cache.  Add code to the onUnbind method to notify other objects of the need for this object to be released.");
	}

	synchronized void read () {
		unlock();
		status = READ;
	}
	synchronized void create () throws PersistenceException {
		lock();
		onCreate();
		status = CREATE;
	}
	synchronized protected void edit () throws LockException,PersistenceException,ValidationException {
		if (status != READ) return;
		attemptLock();
		onEdit();
		status = EDIT;
	}
	synchronized public void delete () throws LockException,PersistenceException,ValidationException {
		if (status == READ) attemptLock();
		onDelete();
		status = DELETE;
	}
	synchronized void persist () throws LockException,PersistenceException,ValidationException {
		onPersist();
	}

	public static synchronized void unload (StringBuffer sb,String name) throws PersistenceException {
		sb.append("DELETE FROM ").append(name).append(";\r\n");
		Domain[] domains = DomainPort.selectAll(name);
		Persist persist = Controller.controller.getPersist(name);
		for (Domain domain : domains)
			sb.append(persist.unload(domain));
	}

	private synchronized void attemptLock () throws LockException {
		if (status == READ) lock();
		else if (!Runner.getTask().equals(task)) throw new LockException(this);
	}
	private synchronized void lock () {
		if (!bound) throw new RuntimeException("Construction or modifications of an unbound domain object have been attempted.  Domain objects can not be constructed directly; use DomainPort to create or select Domain Objects.  Also, changes can not be made to domain objects that are not in the cache.  Domain Objects should not be held on to after they have been removed from the cache.  Add code to the onUnbind method to notify other objects of the need for this object to be released.");
		task = Runner.getTask();
		if (task == null) throw new RuntimeException("No Task active...Changes to the persistence layer must occur within a Task");
		task.addDomain(this);
	}
	private synchronized void unlock () {
		task = null;
	}

	public Map toMap () {
		return DomainMapper.toMap(this);
	}

	private DomainInfo getDomainInfo () {
		DomainInfo info = infos.get(getDomain());
		if (info == null) {
			info = new DomainInfo(this.getClass());
			infos.put(getDomain(),info);
		}
		return info;
	}
	public void setByIntrospection (String property,String value) {
		DomainInfo info = getDomainInfo();
		Method setter = info.getSetter(property);
		if (setter!= null && setter.getParameterTypes().length == 1) {
			String type = setter.getParameterTypes()[0].getName();
			StringTo stringTo = Controller.controller.getStringTo(type);
			Object param = stringTo.fromString(value);
			setByIntrospection(property,param);
		}
	}
	public void setByIntrospection (String property,Object value) {
		try {
			DomainInfo info = getDomainInfo();
			Method setter = info.getSetter(property);
			setter.invoke(this,new Object[]{value});
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		}
	}
	public Object getByIntrospection (String property) {
		try {
			DomainInfo info = getDomainInfo();
			Method getter = info.getGetter(property);
			return getter.invoke(this,new Object[]{});
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		}
	}

// Static ======================================================================
	final static int LOADING	= 0;
	final static int READ		= 1;
	final static int CREATE		= 2;
	final static int EDIT		= 3;
	final static int DELETE		= 4;

	private final static Map<String,DomainInfo> infos = new HashMap<String,DomainInfo>();
}

